var CHAR_TILDE = 126;
var CODE_FNC1 = 102;
var SET_STARTA = 103;
var SET_STARTB = 104;
var SET_STARTC = 105;
var SET_SHIFT = 98;
var SET_CODEA = 101;
var SET_CODEB = 100;
var SET_STOP = 106;
var REPLACE_CODES = {
	CHAR_TILDE: CODE_FNC1 //~ corresponds to FNC1 in GS1-128 standard

};
var CODESET = {
	ANY: 1,
	AB: 2,
	A: 3,
	B: 4,
	C: 5
};

function getBytes(str) {
	var bytes = [];

	for (var i = 0; i < str.length; i++) {
		bytes.push(str.charCodeAt(i));
	}

	return bytes;
}

exports.code128 = function(ctx, text, width, height) {
	width = parseInt(width);
	height = parseInt(height);
	console.log(ctx, text, width, height, '------------------')
	var codes = stringToCode128(text);
	var g = new Graphics(ctx, width, height);
	var barWeight = g.area.width / ((codes.length - 3) * 11 + 35);
	var x = g.area.left;
	var y = g.area.top;

	for (var i = 0; i < codes.length; i++) {
		var c = codes[i]; //two bars at a time: 1 black and 1 white

		for (var bar = 0; bar < 8; bar += 2) {
			var barW = PATTERNS[c][bar] * barWeight; // var barH = height - y - this.border;

			var barH = height - y;
			var spcW = PATTERNS[c][bar + 1] * barWeight; //no need to draw if 0 width

			if (barW > 0) {
				g.fillFgRect(x, y, barW, barH);
			}

			x += barW + spcW;
		}
	}

	ctx.draw();
};

function stringToCode128(text) {
	var barc = {
		currcs: CODESET.C
	};
	var bytes = getBytes(text); //decide starting codeset

	var index = bytes[0] == CHAR_TILDE ? 1 : 0;
	var csa1 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB;
	var csa2 = bytes.length > 0 ? codeSetAllowedFor(bytes[index++]) : CODESET.AB;
	barc.currcs = getBestStartSet(csa1, csa2);
	barc.currcs = perhapsCodeC(bytes, barc.currcs); //if no codeset changes this will end up with bytes.length+3
	//start, checksum and stop

	var codes = new Array();

	switch (barc.currcs) {
		case CODESET.A:
			codes.push(SET_STARTA);
			break;

		case CODESET.B:
			codes.push(SET_STARTB);
			break;

		default:
			codes.push(SET_STARTC);
			break;
	}

	for (var i = 0; i < bytes.length; i++) {
		var b1 = bytes[i]; //get the first of a pair
		//should we translate/replace

		if (b1 in REPLACE_CODES) {
			codes.push(REPLACE_CODES[b1]);
			i++; //jump to next

			b1 = bytes[i];
		} //get the next in the pair if possible


		var b2 = bytes.length > i + 1 ? bytes[i + 1] : -1;
		codes = codes.concat(codesForChar(b1, b2, barc.currcs)); //code C takes 2 chars each time

		if (barc.currcs == CODESET.C) i++;
	} //calculate checksum according to Code 128 standards


	var checksum = codes[0];

	for (var weight = 1; weight < codes.length; weight++) {
		checksum += weight * codes[weight];
	}

	codes.push(checksum % 103);
	codes.push(SET_STOP); //encoding should now be complete

	return codes;

	function getBestStartSet(csa1, csa2) {
		//tries to figure out the best codeset
		//to start with to get the most compact code
		var vote = 0;
		vote += csa1 == CODESET.A ? 1 : 0;
		vote += csa1 == CODESET.B ? -1 : 0;
		vote += csa2 == CODESET.A ? 1 : 0;
		vote += csa2 == CODESET.B ? -1 : 0; //tie goes to B due to my own predudices

		return vote > 0 ? CODESET.A : CODESET.B;
	}

	function perhapsCodeC(bytes, codeset) {
		for (var i = 0; i < bytes.length; i++) {
			var b = bytes[i];
			if ((b < 48 || b > 57) && b != CHAR_TILDE) return codeset;
		}

		return CODESET.C;
	} //chr1 is current byte
	//chr2 is the next byte to process. looks ahead.


	function codesForChar(chr1, chr2, currcs) {
		var result = [];
		var shifter = -1;

		if (charCompatible(chr1, currcs)) {
			if (currcs == CODESET.C) {
				if (chr2 == -1) {
					shifter = SET_CODEB;
					currcs = CODESET.B;
				} else if (chr2 != -1 && !charCompatible(chr2, currcs)) {
					//need to check ahead as well
					if (charCompatible(chr2, CODESET.A)) {
						shifter = SET_CODEA;
						currcs = CODESET.A;
					} else {
						shifter = SET_CODEB;
						currcs = CODESET.B;
					}
				}
			}
		} else {
			//if there is a next char AND that next char is also not compatible
			if (chr2 != -1 && !charCompatible(chr2, currcs)) {
				//need to switch code sets
				switch (currcs) {
					case CODESET.A:
						shifter = SET_CODEB;
						currcs = CODESET.B;
						break;

					case CODESET.B:
						shifter = SET_CODEA;
						currcs = CODESET.A;
						break;
				}
			} else {
				//no need to shift code sets, a temporary SHIFT will suffice
				shifter = SET_SHIFT;
			}
		} //ok some type of shift is nessecary


		if (shifter != -1) {
			result.push(shifter);
			result.push(codeValue(chr1));
		} else {
			if (currcs == CODESET.C) {
				//include next as well
				result.push(codeValue(chr1, chr2));
			} else {
				result.push(codeValue(chr1));
			}
		}

		barc.currcs = currcs;
		return result;
	}
} //reduce the ascii code to fit into the Code128 char table


function codeValue(chr1, chr2) {
	if (typeof chr2 == "undefined") {
		return chr1 >= 32 ? chr1 - 32 : chr1 + 64;
	} else {
		return parseInt(String.fromCharCode(chr1) + String.fromCharCode(chr2));
	}
}

function charCompatible(chr, codeset) {
	var csa = codeSetAllowedFor(chr);
	if (csa == CODESET.ANY) return true; //if we need to change from current

	if (csa == CODESET.AB) return true;
	if (csa == CODESET.A && codeset == CODESET.A) return true;
	if (csa == CODESET.B && codeset == CODESET.B) return true;
	return false;
}

function codeSetAllowedFor(chr) {
	if (chr >= 48 && chr <= 57) {
		//0-9
		return CODESET.ANY;
	} else if (chr >= 32 && chr <= 95) {
		//0-9 A-Z
		return CODESET.AB;
	} else {
		//if non printable
		return chr < 32 ? CODESET.A : CODESET.B;
	}
}

var Graphics = function(ctx, width, height) {
	this.width = width;
	this.height = height;
	this.quiet = Math.round(this.width / 40);
	this.border_size = 0;
	this.padding_width = 0;
	this.area = {
		width: width - this.padding_width * 2 - this.quiet * 2,
		height: height - this.border_size * 2,
		top: this.border_size - 4,
		left: this.padding_width + this.quiet
	};
	this.ctx = ctx;
	this.fg = "#000000";
	this.bg = "#ffffff"; // fill background

	this.fillBgRect(0, 0, width, height); // fill center to create border

	this.fillBgRect(0, this.border_size, width, height - this.border_size * 2);
}; //use native color


Graphics.prototype._fillRect = function(x, y, width, height, color) {
	this.ctx.setFillStyle(color);
	this.ctx.fillRect(x, y, width, height);
};

Graphics.prototype.fillFgRect = function(x, y, width, height) {
	this._fillRect(x, y, width, height, this.fg);
};

Graphics.prototype.fillBgRect = function(x, y, width, height) {
	this._fillRect(x, y, width, height, this.bg);
};

var PATTERNS = [
	[2, 1, 2, 2, 2, 2, 0, 0], // 0
	[2, 2, 2, 1, 2, 2, 0, 0], // 1
	[2, 2, 2, 2, 2, 1, 0, 0], // 2
	[1, 2, 1, 2, 2, 3, 0, 0], // 3
	[1, 2, 1, 3, 2, 2, 0, 0], // 4
	[1, 3, 1, 2, 2, 2, 0, 0], // 5
	[1, 2, 2, 2, 1, 3, 0, 0], // 6
	[1, 2, 2, 3, 1, 2, 0, 0], // 7
	[1, 3, 2, 2, 1, 2, 0, 0], // 8
	[2, 2, 1, 2, 1, 3, 0, 0], // 9
	[2, 2, 1, 3, 1, 2, 0, 0], // 10
	[2, 3, 1, 2, 1, 2, 0, 0], // 11
	[1, 1, 2, 2, 3, 2, 0, 0], // 12
	[1, 2, 2, 1, 3, 2, 0, 0], // 13
	[1, 2, 2, 2, 3, 1, 0, 0], // 14
	[1, 1, 3, 2, 2, 2, 0, 0], // 15
	[1, 2, 3, 1, 2, 2, 0, 0], // 16
	[1, 2, 3, 2, 2, 1, 0, 0], // 17
	[2, 2, 3, 2, 1, 1, 0, 0], // 18
	[2, 2, 1, 1, 3, 2, 0, 0], // 19
	[2, 2, 1, 2, 3, 1, 0, 0], // 20
	[2, 1, 3, 2, 1, 2, 0, 0], // 21
	[2, 2, 3, 1, 1, 2, 0, 0], // 22
	[3, 1, 2, 1, 3, 1, 0, 0], // 23
	[3, 1, 1, 2, 2, 2, 0, 0], // 24
	[3, 2, 1, 1, 2, 2, 0, 0], // 25
	[3, 2, 1, 2, 2, 1, 0, 0], // 26
	[3, 1, 2, 2, 1, 2, 0, 0], // 27
	[3, 2, 2, 1, 1, 2, 0, 0], // 28
	[3, 2, 2, 2, 1, 1, 0, 0], // 29
	[2, 1, 2, 1, 2, 3, 0, 0], // 30
	[2, 1, 2, 3, 2, 1, 0, 0], // 31
	[2, 3, 2, 1, 2, 1, 0, 0], // 32
	[1, 1, 1, 3, 2, 3, 0, 0], // 33
	[1, 3, 1, 1, 2, 3, 0, 0], // 34
	[1, 3, 1, 3, 2, 1, 0, 0], // 35
	[1, 1, 2, 3, 1, 3, 0, 0], // 36
	[1, 3, 2, 1, 1, 3, 0, 0], // 37
	[1, 3, 2, 3, 1, 1, 0, 0], // 38
	[2, 1, 1, 3, 1, 3, 0, 0], // 39
	[2, 3, 1, 1, 1, 3, 0, 0], // 40
	[2, 3, 1, 3, 1, 1, 0, 0], // 41
	[1, 1, 2, 1, 3, 3, 0, 0], // 42
	[1, 1, 2, 3, 3, 1, 0, 0], // 43
	[1, 3, 2, 1, 3, 1, 0, 0], // 44
	[1, 1, 3, 1, 2, 3, 0, 0], // 45
	[1, 1, 3, 3, 2, 1, 0, 0], // 46
	[1, 3, 3, 1, 2, 1, 0, 0], // 47
	[3, 1, 3, 1, 2, 1, 0, 0], // 48
	[2, 1, 1, 3, 3, 1, 0, 0], // 49
	[2, 3, 1, 1, 3, 1, 0, 0], // 50
	[2, 1, 3, 1, 1, 3, 0, 0], // 51
	[2, 1, 3, 3, 1, 1, 0, 0], // 52
	[2, 1, 3, 1, 3, 1, 0, 0], // 53
	[3, 1, 1, 1, 2, 3, 0, 0], // 54
	[3, 1, 1, 3, 2, 1, 0, 0], // 55
	[3, 3, 1, 1, 2, 1, 0, 0], // 56
	[3, 1, 2, 1, 1, 3, 0, 0], // 57
	[3, 1, 2, 3, 1, 1, 0, 0], // 58
	[3, 3, 2, 1, 1, 1, 0, 0], // 59
	[3, 1, 4, 1, 1, 1, 0, 0], // 60
	[2, 2, 1, 4, 1, 1, 0, 0], // 61
	[4, 3, 1, 1, 1, 1, 0, 0], // 62
	[1, 1, 1, 2, 2, 4, 0, 0], // 63
	[1, 1, 1, 4, 2, 2, 0, 0], // 64
	[1, 2, 1, 1, 2, 4, 0, 0], // 65
	[1, 2, 1, 4, 2, 1, 0, 0], // 66
	[1, 4, 1, 1, 2, 2, 0, 0], // 67
	[1, 4, 1, 2, 2, 1, 0, 0], // 68
	[1, 1, 2, 2, 1, 4, 0, 0], // 69
	[1, 1, 2, 4, 1, 2, 0, 0], // 70
	[1, 2, 2, 1, 1, 4, 0, 0], // 71
	[1, 2, 2, 4, 1, 1, 0, 0], // 72
	[1, 4, 2, 1, 1, 2, 0, 0], // 73
	[1, 4, 2, 2, 1, 1, 0, 0], // 74
	[2, 4, 1, 2, 1, 1, 0, 0], // 75
	[2, 2, 1, 1, 1, 4, 0, 0], // 76
	[4, 1, 3, 1, 1, 1, 0, 0], // 77
	[2, 4, 1, 1, 1, 2, 0, 0], // 78
	[1, 3, 4, 1, 1, 1, 0, 0], // 79
	[1, 1, 1, 2, 4, 2, 0, 0], // 80
	[1, 2, 1, 1, 4, 2, 0, 0], // 81
	[1, 2, 1, 2, 4, 1, 0, 0], // 82
	[1, 1, 4, 2, 1, 2, 0, 0], // 83
	[1, 2, 4, 1, 1, 2, 0, 0], // 84
	[1, 2, 4, 2, 1, 1, 0, 0], // 85
	[4, 1, 1, 2, 1, 2, 0, 0], // 86
	[4, 2, 1, 1, 1, 2, 0, 0], // 87
	[4, 2, 1, 2, 1, 1, 0, 0], // 88
	[2, 1, 2, 1, 4, 1, 0, 0], // 89
	[2, 1, 4, 1, 2, 1, 0, 0], // 90
	[4, 1, 2, 1, 2, 1, 0, 0], // 91
	[1, 1, 1, 1, 4, 3, 0, 0], // 92
	[1, 1, 1, 3, 4, 1, 0, 0], // 93
	[1, 3, 1, 1, 4, 1, 0, 0], // 94
	[1, 1, 4, 1, 1, 3, 0, 0], // 95
	[1, 1, 4, 3, 1, 1, 0, 0], // 96
	[4, 1, 1, 1, 1, 3, 0, 0], // 97
	[4, 1, 1, 3, 1, 1, 0, 0], // 98
	[1, 1, 3, 1, 4, 1, 0, 0], // 99
	[1, 1, 4, 1, 3, 1, 0, 0], // 100
	[3, 1, 1, 1, 4, 1, 0, 0], // 101
	[4, 1, 1, 1, 3, 1, 0, 0], // 102
	[2, 1, 1, 4, 1, 2, 0, 0], // 103
	[2, 1, 1, 2, 1, 4, 0, 0], // 104
	[2, 1, 1, 2, 3, 2, 0, 0], // 105
	[2, 3, 3, 1, 1, 1, 2, 0] // 106
];
